#include <sys/param.h>
#include "esp_event.h"
#include "esp_log.h"
#include "esp_mac.h"
#include "nvs_flash.h"
#include "esp_wifi.h"
#include "esp_netif.h"
#include "lwip/inet.h"
#include "cJSON.h"
#include "esp_http_server.h"
#include "dns_server.h"
#include <stdio.h>
#include <string.h>
#include "esp_vfs_semihost.h"
#include "esp_vfs_fat.h"
#include "esp_spiffs.h"
#define ESP_WIFI_SSID "ESP32_WIFI"   
#define ESP_WIFI_PASS "123456789"
#define MAX_STA_CONN 4


#define NUM_OUTPUTS  4
int outputGPIOs[NUM_OUTPUTS] = {2, 4, 12, 14}; 

static const char *param_output = "output";
static const char *param_state = "state";
static const char *TAG = "WIFI_WEB";   

//ESP_VFS_PATH_MAX  15
#define FILE_PATH_MAX (ESP_VFS_PATH_MAX + 128)
#define SCRATCH_BUFSIZE (10240)
typedef struct rest_server_context {
    char base_path[ESP_VFS_PATH_MAX + 1];
    char scratch[SCRATCH_BUFSIZE];
} rest_server_context_t;

//刷新界面
static void redirect_index_html(httpd_req_t *req)
{
    httpd_resp_set_status(req, "307 Temporary Redirect");
    httpd_resp_set_hdr(req, "Location", "/");
	//Response body can be empty
    httpd_resp_send(req, NULL, 0);  
}

//十六进制转化为数字
/*十六进制转化为数字*/
static int httpdHexVal(char c)
{
    if (c >= '0' && c <= '9') {
        return c - '0';
    }
    if (c >= 'A' && c <= 'F') {
        return c - 'A' + 10;
    }
    if (c >= 'a' && c <= 'f') {
        return c - 'a' + 10;
    }
    return 0;
}

//URL解码
//"Hello%2C%20World%21%0A"
int httpdUrlDecode(char* val, int valLen, char* ret, int retLen)
{
    int s = 0, d = 0;
    int esced = 0, escVal = 0;
    while (s < valLen && d < retLen) {
        if (esced == 1)  {
            escVal = httpdHexVal(val[s]) << 4;
            esced = 2;
        } else if (esced == 2) {
            escVal += httpdHexVal(val[s]);
            ret[d++] = escVal;
            esced = 0;
        } else if (val[s] == '%') {
            esced = 1;
        } else if (val[s] == '+') {
            ret[d++] = ' ';   //
        } else {
            ret[d++] = val[s];//
        }
        s++;
    }
    if (d < retLen) {
        ret[d] = 0;
    }
    return d;
}

//查找
//update?output=4&state=1
int httpd_find_arg(const char* line, const char* arg, char* buff, int buffLen)
{
    char* p, *e;
    bool first_in = true;
    if (line == NULL) {
        return -1;
    }
    p = line;
	//判断
    while (p != NULL && *p != '\n' && *p != '\r' && *p != 0) {
		//比较前n个字节
		//move p to start of value
        if (strncmp(p, arg, strlen(arg)) == 0 && p[strlen(arg)] == '=') {
            p += strlen(arg) + 1; 
            e = (char*)strstr(p, "&");

            if (e == NULL) {
                e = p + strlen(p);
            }
            // printf("findArg: val %s len %d\n", p, (e-p));
            return httpdUrlDecode(p, (e - p), buff, buffLen);
        }

        if(first_in) {
            p = (char*)strstr(p, "?");
            first_in = false;
        } else {
            p = (char*)strstr(p, "&");
        }
        
        if (p != NULL) {
            p += 1;
        }
    }
    return -1; //not found
}


#define CHECK_FILE_EXTENSION(filename, ext) (strcasecmp(&filename[strlen(filename) - strlen(ext)], ext) == 0)

/* Set HTTP response content type according to file extension */
//设置HTTP响应内容类型  shell.html  //10-5=5 
static esp_err_t set_content_type_from_file(httpd_req_t* req, const char* filepath)
{
    const char* type = "text/plain";
    if (CHECK_FILE_EXTENSION(filepath, ".html")) {
        type = "text/html";
    } else if (CHECK_FILE_EXTENSION(filepath, ".js")) {
        type = "application/javascript";
    } else if (CHECK_FILE_EXTENSION(filepath, ".css")) {
        type = "text/css";
    } else if (CHECK_FILE_EXTENSION(filepath, ".png")) {
        type = "image/png";
    } else if (CHECK_FILE_EXTENSION(filepath, ".ico")) {
        type = "image/x-icon";
    } else if (CHECK_FILE_EXTENSION(filepath, ".svg")) {
        type = "text/xml";
    }
    return httpd_resp_set_type(req, type);
}

/* Send HTTP response with the contents of the requested file */
static esp_err_t rest_common_get_handler(httpd_req_t* req)
{
	ESP_LOGI(TAG, "uri: %s\n", req->uri);
    char filepath[FILE_PATH_MAX];
    rest_server_context_t* rest_context = (rest_server_context_t*) req->user_ctx;
    strlcpy(filepath, rest_context->base_path, sizeof(filepath));
    if (req->uri[strlen(req->uri) - 1] == '/') {
        strlcat(filepath, "/web_image/root.html", sizeof(filepath));
    } else {
		strlcat(filepath, "/web_image", sizeof(filepath));
        strlcat(filepath, req->uri, sizeof(filepath));
    }

    char* p = strrchr(filepath, '?');
    if (p != NULL) {
        *p = '\0';
    }

    int fd = open(filepath, O_RDONLY, 0);
    if (fd == -1) {
        ESP_LOGE(TAG, "Failed to open file : %s", filepath);
        /* Respond with 500 Internal Server Error */
        httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to read existing file");
        return ESP_FAIL;
    }

    set_content_type_from_file(req, filepath);

    char* chunk = rest_context->scratch;
    ssize_t read_bytes;
    do {
        /* Read file in chunks into the scratch buffer */
        read_bytes = read(fd, chunk, SCRATCH_BUFSIZE);
        if (read_bytes == -1) {
            ESP_LOGE(TAG, "Failed to read file : %s", filepath);
        } else if (read_bytes > 0) {
            /* Send the buffer contents as HTTP response chunk */
            if (httpd_resp_send_chunk(req, chunk, read_bytes) != ESP_OK) {
                close(fd);
                ESP_LOGE(TAG, "File sending failed!");
                /* Abort sending file */
                httpd_resp_sendstr_chunk(req, NULL);
                /* Respond with 500 Internal Server Error */
                httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to send file");
                return ESP_FAIL;
            }
        }
    } while (read_bytes > 0);
    /* Close file after sending complete */
    close(fd);
    ESP_LOGI(TAG, "File sending complete");
    /* Respond with an empty chunk to signal HTTP response completion */
    httpd_resp_send_chunk(req, NULL, 0);
    return ESP_OK;
}

/* A help function to get post request data */
static esp_err_t recv_post_data(httpd_req_t* req, char* buf)
{
    int total_len = req->content_len;
    int cur_len = 0;
    int received = 0;

    if (total_len >= SCRATCH_BUFSIZE) {
        /* Respond with 500 Internal Server Error */
        httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "content too long");
        return ESP_FAIL;
    }
    while (cur_len < total_len) {
        received = httpd_req_recv(req, buf + cur_len, total_len);
        if (received <= 0) {
            /* Respond with 500 Internal Server Error */
            httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to post control value");
            return ESP_FAIL;
        }
        cur_len += received;
    }
    buf[total_len] = '\0';//now ,the post is str format, like ssid=yuxin&pwd=TestPWD&chl=1&ecn=0&maxconn=1&ssidhidden=0
    ESP_LOGI(TAG, "Post data is : %s\n", buf);
    return ESP_OK;
}

#define TEMP_STR_MAX_LEN (64)

// Return JSON with Current Output States
char *getOutputStates() 
{
    char temp_str1[TEMP_STR_MAX_LEN] = {0};
    char temp_str2[TEMP_STR_MAX_LEN] = {0};
    char temp_str3[TEMP_STR_MAX_LEN] = {0};
    int body_len1 = 0;
    int body_len2 = 0;
	
    cJSON* root = cJSON_CreateObject();
    cJSON *gpio_array = cJSON_CreateArray();
    cJSON_AddItemToObject(root, "gpios", gpio_array);

    for (int i =0; i<NUM_OUTPUTS; i++) {
        memset(temp_str1, '\0', TEMP_STR_MAX_LEN * sizeof(char));
        memset(temp_str2, '\0', TEMP_STR_MAX_LEN * sizeof(char));
        memset(temp_str3, '\0', TEMP_STR_MAX_LEN * sizeof(char));
        body_len1 = 0;
        body_len2 = 0;
        cJSON *gpio_item = cJSON_CreateObject();
        cJSON_AddItemToArray(gpio_array, gpio_item);
        body_len1 += sprintf((char *)temp_str1 + body_len1, "output");
        body_len2 += sprintf((char *)temp_str2 + body_len2, "state");

        itoa(outputGPIOs[i], temp_str3, 10);
        cJSON_AddStringToObject(gpio_item, temp_str1, temp_str3);

        itoa(0, temp_str3, 10);
        cJSON_AddStringToObject(gpio_item, temp_str2, temp_str3);
    }
    char* printed_json = cJSON_Print(root);
    cJSON_Delete(root);
    return printed_json;
}

//发送数据
static esp_err_t output_states_get_handler(httpd_req_t *req)
{
    char* printed_json = getOutputStates();
    ESP_LOGI(TAG, "get system info:%s\n", printed_json);
    httpd_resp_set_type(req, "application/json");
    httpd_resp_sendstr(req, printed_json);
    free((void*) printed_json);
    return ESP_OK;
}

//跟新状态
static esp_err_t update_state_get_handler(httpd_req_t *req)
{
    char temp_str[64] = {0};
    int str_len = 0;
    int gpio_num = -1;
    int gpio_state = -1;

    ESP_LOGI(TAG, "req:%s", req->uri);

    str_len = httpd_find_arg(req->uri, param_output, temp_str, sizeof(temp_str));
    if ((str_len == -1) || (strlen((char *)temp_str) == 0)) {
        ESP_LOGE(TAG, "no gpio output message find");
    } else {
        gpio_num = atoi(temp_str);
        ESP_LOGI(TAG, "updates:gpio_num=%d", gpio_num);
    }

    memset(temp_str, '\0', 64 * sizeof(char));

    if(gpio_num != -1) {
        str_len = httpd_find_arg(req->uri, param_state, temp_str, sizeof(temp_str));
        if ((str_len == -1) || (strlen((char *)temp_str) == 0)) {
            ESP_LOGE(TAG, "no status find");
        } else {
            gpio_state = atoi(temp_str);
            redirect_index_html(req); // You must reload the webpage, otherwise there may be a significant delay in the next operation
            ESP_LOGI(TAG, "updates:gpio[%d]_status=%d", gpio_num, gpio_state);
        }
    }
    return ESP_OK;
}

static const char web_base_point[] = "/spiffs";

esp_err_t init_fs(void)
{
    esp_vfs_spiffs_conf_t conf = {
        .base_path = web_base_point,     //文件挂载点路径
        .partition_label = NULL,         //分区标签，设为NULL表示默认分区
        .max_files = 5,                  //最大同时打开文件数量
        .format_if_mount_failed = false  //挂载失败时是否格式化分区，这里设为false
    };
	//注册并挂载 SPIFFS 文件系统
    esp_err_t ret = esp_vfs_spiffs_register(&conf);

    if (ret != ESP_OK) {
        if (ret == ESP_FAIL) {
            ESP_LOGE(TAG, "Failed to mount or format filesystem");
        } else if (ret == ESP_ERR_NOT_FOUND) {
            ESP_LOGE(TAG, "Failed to find SPIFFS partition");
        } else {
            ESP_LOGE(TAG, "Failed to initialize SPIFFS (%s)", esp_err_to_name(ret));
        }
        return ESP_FAIL;
    }

    size_t total = 0, used = 0;
	//获取分区的总大小和已使用空间，并打印信息
    ret = esp_spiffs_info(NULL, &total, &used);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to get SPIFFS partition information (%s)", esp_err_to_name(ret));
    } else {
        ESP_LOGI(TAG, "Partition size: total: %d, used: %d", total, used);
    }
    return ESP_OK;
}

static void wifi_event_handler(void *arg, esp_event_base_t event_base,
                               int32_t event_id, void *event_data)
{
    if (event_id == WIFI_EVENT_AP_STACONNECTED) {
        wifi_event_ap_staconnected_t *event = (wifi_event_ap_staconnected_t *)event_data;
        ESP_LOGI(TAG, "station " MACSTR " join, AID=%d",
                 MAC2STR(event->mac), event->aid);
    } else if (event_id == WIFI_EVENT_AP_STADISCONNECTED) {
        wifi_event_ap_stadisconnected_t *event = (wifi_event_ap_stadisconnected_t *)event_data;
        ESP_LOGI(TAG, "station " MACSTR " leave, AID=%d",
                 MAC2STR(event->mac), event->aid);
    }
}

//WIFI AP模式初始化
static void wifi_init_softap(void)
{
	//使用默认配置初始化WIFI
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
	//WIFI初始化
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));
	//注册回调函数
    ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL));
	//WIFI信息结构体
    wifi_config_t wifi_config = {
        .ap = {
            .ssid = ESP_WIFI_SSID,                     //wifi 名字
            .ssid_len = strlen(ESP_WIFI_SSID),         //WiFi 名称长度
            .password = ESP_WIFI_PASS,                 //WiFi 密码
            .max_connection = MAX_STA_CONN,            //wifi 最大连接数量 
            .authmode = WIFI_AUTH_WPA_WPA2_PSK         //wifi 加密方式
        },
    };
    if (strlen(ESP_WIFI_PASS) == 0) {
        wifi_config.ap.authmode = WIFI_AUTH_OPEN;
    }

	//设置WIFI操作方式
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
	//设置AP的配置
    ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_config));
	//启动按照当前的配置  
    ESP_ERROR_CHECK(esp_wifi_start());
	//IP数据结构体
    esp_netif_ip_info_t ip_info;
    esp_netif_get_ip_info(esp_netif_get_handle_from_ifkey("WIFI_AP_DEF"), &ip_info);

    char ip_addr[16];
    inet_ntoa_r(ip_info.ip.addr,ip_addr, 16);
    ESP_LOGI(TAG, "Set up softAP with IP: %s", ip_addr);
}


// HTTP Error (404) Handler - Redirects all requests to the root page
esp_err_t http_404_error_handler(httpd_req_t *req, httpd_err_code_t err)
{
    // Set status
    httpd_resp_set_status(req, "302 Temporary Redirect");
    // Redirect to the "/" root directory
    httpd_resp_set_hdr(req, "Location", "/");
    // iOS requires content in the response to detect a captive portal, simply redirecting is not sufficient.
    httpd_resp_send(req, "Redirect to the captive portal", HTTPD_RESP_USE_STRLEN);
    return ESP_OK;
}

//启动Web服务器
static httpd_handle_t start_webserver(void)
{
    rest_server_context_t* rest_context = calloc(1, sizeof(rest_server_context_t));
    if(rest_context==NULL) 
		return NULL;
	ESP_LOGI(TAG, "rest_context success");
    strlcpy(rest_context->base_path, web_base_point, sizeof(rest_context->base_path));

    httpd_handle_t server = NULL;
    httpd_config_t config = HTTPD_DEFAULT_CONFIG();
    config.max_open_sockets = 7;
	config.max_uri_handlers = 16;
    config.lru_purge_enable = true;

	httpd_uri_t httpd_uri_array[] = {
		{"/states", HTTP_GET, output_states_get_handler, rest_context},
        {"/update", HTTP_GET, update_state_get_handler, rest_context},
        {"/", HTTP_GET, rest_common_get_handler,rest_context},
		{"/script.js", HTTP_GET, rest_common_get_handler,rest_context},
		{"/style.css", HTTP_GET, rest_common_get_handler,rest_context},
		{"/favicon.ico", HTTP_GET, rest_common_get_handler,rest_context}
    };
    // Start the httpd server
    //启动httpd server服务器
    if (httpd_start(&server, &config) == ESP_OK) {

			for(int i = 0; i < sizeof(httpd_uri_array)/sizeof(httpd_uri_t); i++){
			if (httpd_register_uri_handler(server, &httpd_uri_array[i]) != ESP_OK) {
				ESP_LOGE(TAG, "httpd register uri_array[%d] fail", i);
			}
		}

    }
    return server;
}

void app_main(void)
{
	
    //设置 httpd_uri 到错误水平
    esp_log_level_set("httpd_uri", ESP_LOG_ERROR);
    esp_log_level_set("httpd_txrx", ESP_LOG_ERROR);
    esp_log_level_set("httpd_parse", ESP_LOG_ERROR);
    // Initialize networking stack
	//初始化网络堆栈
    ESP_ERROR_CHECK(esp_netif_init());

    //Create default event loop needed by the  main app
	//创建主应用程序所需要的事件循环
    ESP_ERROR_CHECK(esp_event_loop_create_default());

    //Initialize NVS needed by Wi-Fi
	//初始化WIFI所需要的NVS
    ESP_ERROR_CHECK(nvs_flash_init());

    //Initialize Wi-Fi including netif with default config
	//用默认配置初始化包含netif的WI-FI
    esp_netif_create_default_wifi_ap();

    //Initialise ESP32 in SoftAP mode
	//初始化ESP32用AP模式
    wifi_init_softap();

	init_fs();

    // Start the server for the first time
	//首次启动服务器
    start_webserver();

    //Start the DNS server that will redirect all queries to the softAP IP
	//启动DNS服务器，将所有查询重定向到softAP IP
    dns_server_config_t config = DNS_SERVER_CONFIG_SINGLE("*" /* all A queries */, "WIFI_AP_DEF" /* softAP netif ID */);
	//启动DNS服务器
    start_dns_server(&config);

}
